import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * This is the VSM machine code generator. You only need to know the <b>genCode</b>  method. 
 * Read it if you are curious but it is pretty hairy!!
 * @author leborgne
 *
 */
public class VSMCodeGenerator implements CGConstants , VSMCGConstants{
	static int P_OFF = 0;
	static int PC_OFF = 4;
	static int VAR_OFF = 8;
	
	private VSMRegAlloc regalloc;
	private int topOfStack;
	private int nextArg;
	private PrintStream out;
	
	// VSM assembler doesn't like arbitrary label names only Ln
	// so we need to replace them
	Map<String, String> LabelReplacement = new HashMap<String, String>();
	
	public VSMCodeGenerator(PrintStream out) {
		regalloc = new VSMRegAlloc();
		topOfStack = VAR_OFF;
		nextArg = 0;
		this.out = out;
	}
	
	/**
	 * normalize label names to Ln
	 */
	public void preprocess(Code3a c){
		int cpt = 10; // first ten labels are reserved
		Vector<Inst3a> cc = c.getCode();
		// no more label than statements
		cpt = cpt + cc.size();
		for (Inst3a inst : cc){
			if (inst.getOp()==TAC_LABEL){
				String labname = inst.getA().getName3a();
				if (!(labname.matches("L[0-9]+"))){
					cpt = cpt + 1;
					String nlabname = "L"+String.valueOf(cpt);
					LabelReplacement.put(labname, nlabname);
				}
			}
		}
	}
	
	/*
	 * get normalized label name
	 */
	String  normLabelName(String name){
		String s = this.LabelReplacement.get(name);
		if (s != null)
			return s;
		else
			return name;
	}
	
	/**
	 * generate assembly code from 3a code.
	 * Output it on <b> stdout </b>
	 */
	public void genCode(Code3a c){
		int i;
		this.preprocess(c);
		Vector<Inst3a> cc = c.getCode();
		// common header
		cg_sys("/share/m1info/COMP/TP2/vsm/header.s");
		// jump to main
		out.println(R_INDENT + "BRA" + "  " + this.normLabelName("main"));
		// list of instructions
		for (i=0; i<cc.size();i++){
			Inst3a inst = cc.elementAt(i);
			out.println("\\"+ inst.toString());
			genCode(inst);
		}
		// add library assembly code
		cg_sys("/share/m1info/COMP/TP2/vsm/lib.s");
		// add data code
		cg_const(c.the_data);
	}
	/**
	 * This method generates code for a simple TAC instruction. The resulting
	 * code is emitted on standard output.
	 */
	private void genCode(Inst3a c) {
		switch (c.getOp()) {
		case TAC_ADD:
			cg_bin("ADD", c.getA(), c.getB(), c.getC());
			return;

		case TAC_SUB:
			cg_bin("SUB", c.getA(), c.getB(), c.getC());
			return;

		case TAC_MUL:
			cg_bin("MUL", c.getA(), c.getB(), c.getC());
			return;

		case TAC_DIV:
			cg_bin("DIV", c.getA(), c.getB(), c.getC());
			return;

		case TAC_NEG:
			cg_neg(c.getA(), c.getB());
			return;

		case TAC_COPY:
			cg_copy(c.getA(), c.getB());
			return;

		case TAC_GOTO:
			cg_cond("BRA", null, c.getA());
			return;

		case TAC_IFZ:
			cg_cond("BZE", c.getA(), c.getB());
			return;

		case TAC_IFNZ:
			cg_cond("BNZ", c.getA(), c.getB());
			return;

		case TAC_ARG:
			cg_arg(c.getA());
			return;

		case TAC_CALL:
			cg_call(c.getB(), c.getA());
			return;

		case TAC_RETURN:
			cg_return(c.getA());
			return;

		case TAC_LABEL:
			/*
			 * We generate an appropriate label. Note that we must flush the
			 * register descriptor, since control may arrive at this label from
			 * other points in the code.
			 */
			regalloc.flushAll();
			String nlab = this.normLabelName(c.getA().getName3a());
			out.println(nlab + ":");
			return;

		case TAC_VAR:
			/*
			 * Allocate 4*n bytes for this variable to hold an integer (n=1) or
			 * an integer array (n = size) on the current top of stack
			 */
			topOfStack = cg_var(c.getA(), topOfStack);
			return;

		case TAC_BEGINFUNC:
			/*
			 * At the start of a function we must copy the return address which
			 * will be in R_RET onto the stack. We reset the top of stack, since
			 * it is currently empty apart from the link information.
			 */
			topOfStack = VAR_OFF;
			regalloc.flushAll();
			out.println(R_INDENT + "STI  R" + R_RET + "," + PC_OFF
					+ "(R" + R_STACK + ")");
			return;

		case TAC_ENDFUNC:
			/*
			 * At the end of the function we put in an implicit return
			 * instruction.
			 */
			cg_return(null);
			return;

		case TAC_VARTAB:
			cg_vartab(c.getA(), c.getB(), c.getC());
			return;

		case TAC_TABVAR:
			cg_tabvar(c.getA(), c.getB(), c.getC());
			return;

		default: /* Don't know what this one is */
			System.err.println("unknown TAC opcode to translate");
			return;
		}

	}

	/**
	 * Generate code for a binary operator a := b op c VAM has 2 address opcodes
	 * with the result going into the second operand This is a typical code
	 * generation functions. We find and load a separate register for each
	 * argument, the second argument also being used for the result. We then
	 * generate the code for binary operator, updating the register descriptor
	 * appropriately.
	 */
	private void cg_bin(String opcode, /* Opcode to use */
	Operand3a a, /* Result */
	Operand3a b, /* Operands */
	Operand3a c) {
		int cr; /* Result register */
		int br; /* Second argument register */
		int r;

		cr = regalloc.getErasable(c); /* Result register */
		br = regalloc.getAnother(b, cr); /* Second argument register */
		out.println(R_INDENT + opcode + "  R" + br + ",R" + cr);
		r = regalloc.search(a); /* Is a in reg? */
		if (r!=-1) regalloc.clearDescriptor(r);

		/* Delete c from the descriptors and insert a */
		regalloc.clearDescriptor(cr);
		regalloc.setDescriptor(cr, a, R_MODIFIED);
	}

	/**
	 * generate code for unary neg operator a := - b. We use SUB as there is
	 * no unary operator in the instruction set of the VAM 
	 */
	private void cg_neg(Operand3a a, Operand3a b){
		int cr; /* Result register */
		int br; /* Second argument register */
		int r;

		cr = regalloc.getErasable(b); /* Result register */
		br = R_ZERO; /* always contains zero */
		out.println(R_INDENT + "SUB  " + " R" + br + ",R" + cr);
		r = regalloc.search(a); /* Is a in reg? */
		if (r!=-1) regalloc.clearDescriptor(r);
		
		/* Delete c from the descriptors and insert a */
		regalloc.clearDescriptor(cr);
		regalloc.setDescriptor(cr, a, R_MODIFIED);
	}
	/**
	 * Generate code for a copy instruction a := b We load b into an register,
	 * then update the descriptors to indicate that a is also in that register.
	 * We need not do the store until the register is spilled or flushed.
	 * 
	 * @param a
	 * @param b
	 */
	private void cg_copy(Operand3a a, Operand3a b) {
		int br = regalloc.getErasable(b); /* Load b into a register */
		regalloc.setDescriptor(br, a, R_MODIFIED); /* Indicate a is there */
	}

	/**
	 * Generate for "goto", "ifz" or "ifnz". We must spill registers before the
	 * branch. In the case of unconditional goto we have no condition, and so
	 * "b" is NULL. We set the condition flags if necessary by explicitly
	 * loading "a" into a register to ensure the zero flag is set. A better
	 * approach would be to keep track of what is in the status register, so
	 * saving this load.
	 */
	private void cg_cond(String op, Operand3a a, /* Condition */
	Operand3a label) /* Branch destination */
	{
		regalloc.spillAll();
		if (a != null) {
			int r;
			r = regalloc.search(a);
			if (r != -1)
				regalloc.loadRegister(r, a); 
				/* Reload into existing reg (for the flag) */
			else
				r = regalloc.getErasable(a); /* Load into new register */
		}
		out.println(R_INDENT + op + "  " + this.normLabelName(label.getName3a())); /* Branch */

	}

	/**
	 * Allocate 4*sizeof(var) bytes on top of stack
	 */
	private int cg_var(Operand3a var, int offset) {
		int size;

	//	if (var.isVarInteger() || var.isPointer())
	//		size = 1;
	//	else
			size = var.getSize();
		var.setOffset(offset);
		out.println("\\ "+var.getName3a()+"  @ = " + offset);
		return offset + size;
	}

	/**
	 * Generate for an ARG instruction. If the arg. is a pointer or an integer
	 * We load the argument into a register, and then write it onto the new
	 * stack frame, which is 2 past the current top of stack. If it is a local
	 * array, we compute its address and write it into the new frame. We keep
	 * track of which arg this is in the global variable "next_arg". We assume
	 * that ARG instructions are always followed by other ARG instructions or
	 * CALL instructions.
	 */
	private void cg_arg(Operand3a a) {
		int r, offset;
		
		if (a.isArray()) { /* local array */
			r = regalloc.getSome(0);
			offset = a.getOffset();
			out.println(R_INDENT + "LDA  " + offset + "(R" + R_STACK
					+ "),R" + r);
		} /* @a -> r */
		else /* integer variable, integer constant, label or array parameter
		        label parameters have always Ln names */
		{
			r = regalloc.getErasable(a); /* a -> r */
		}
		;
		offset = topOfStack + VAR_OFF + nextArg;
		out.println(R_INDENT + "STI  R" + r + "," + offset + "(R"
				+ R_STACK+")");
		nextArg += WORD_SIZE;
	}

	/**
	 * The standard call sequence is LDA f(R0),R2 STI R1,tos(R1) LDA tos(R1),R1
	 * BAL R2,R3 ( STI R4,res ) We flush out the registers prior to a call and
	 * then execute the standard CALL sequence. Flushing involves spilling
	 * modified registers, and then clearing the register descriptors. We use
	 * BAL to do the call, which means R_RET will hold the return address on
	 * entry to the function which must be saved on the stack. After the call if
	 * there is a result it will be in R_RES so enter this in the descriptors.
	 * We reset "next_arg" before the call, since we know we have finished all
	 * the arguments now.
	 */
	private void cg_call(Operand3a label, Operand3a res) {
		String slabel = this.normLabelName(label.getName3a());

		regalloc.flushAll();
		nextArg = 0;
		out.println(R_INDENT + "LDA  " + slabel + ",R" + R_CALL);
		out.println(R_INDENT + "STI  R" + R_STACK + ","+topOfStack+"(R" + R_STACK
				+ ")");
		out.println(R_INDENT + "LDA  " + topOfStack + "(R" + R_STACK
				+ "),R" + R_STACK);
		out.println(R_INDENT + "BAL  R" + R_CALL + ",R" + R_RET);

		if (res != null) /* Do a result if there is one */
			regalloc.setDescriptor(R_RES, res, R_MODIFIED);

	}

	/**
	 * The standard return sequence is ( LDI a,R4 ) LDI 4(R1),R2 return program
	 * counter LDI 0(R1),R1 return stack pointer BAL R2,R3 If "a" is NULL we
	 * don't load anything into the result register.
	 */

	private void cg_return(Operand3a a) {
		if (a != null) {
			regalloc.spillOne(R_RES);
			regalloc.loadRegister(R_RES, a);
		}
		out.println(R_INDENT + "LDI  " + PC_OFF + "(R" + R_STACK + "),R"
				+ R_CALL);
		out.println(R_INDENT + "LDI  " + P_OFF + "(R" + R_STACK + "),R"
				+ R_STACK);
		out.println(R_INDENT + "BAL  R" + R_CALL + ",R"+ R_RET);
	}

	/**
	 * We compute the address of the array element tab[indice]. If tab is a
	 * local array its address is R_STACK+offset+indice. If it is a pointer, its
	 * address is tab+indice. indice is the offset from the beginning of the
	 * array
	 */

	private int tab_address(Operand3a tab, Operand3a indice) {
		int r1, r2, offset = tab.getOffset();


		r1 = regalloc.getErasable(indice); /* indice -> r1 */		
		r2 = regalloc.getSome(r1);
		out.println(R_INDENT + "LDA  4(R0),R" + r2);  /* 4 -> r2 */
		out.println(R_INDENT + "MUL  R" + r2 + ",R" + r1); /* indice*4 ->r1 */
		if (tab.isPointer()) {
			r2 = regalloc.getAnother(tab, r1); /* tab -> r2 */
			out.println(R_INDENT + "ADD  R" + r2 + ",R" + r1);/* tab+indice*4->r1 */
		} else { /* local array */
			out.println(R_INDENT + "LDA  " + offset + "(R" + r1 + "),R"
					+ r1);/* offset + indice*4 -> r1 */
			out.println(R_INDENT + "ADD  R" + R_STACK + ",R" + r1);
			/*R_STACK +offset+indice -> r1 */									
		}
		;
		return r1; /* register holding tab[indice] address */
	}

	/**
	 * assignments using indice adressing
	 */
	private void cg_vartab(Operand3a tab, Operand3a indice, Operand3a val) {
		int r1, r2;

		r1 = tab_address(tab, indice);
		r2 = regalloc.getAnother(val, r1); /* val -> r2 */
		out.println(R_INDENT + "STI  R" + r2 + ",0(R" + r1 + ")");
		/* r2 ->tab[indice] */
		regalloc.clearDescriptor(r1);
	}

	private void cg_tabvar(Operand3a var, Operand3a tab, Operand3a indice) {
		int r1;

		r1 = tab_address(tab, indice);
		out.println(R_INDENT + "LDI  0(R" + r1 + "),R" + r1);
		/* tab[indice] -> r1 */							
		regalloc.setDescriptor(r1, var, R_MODIFIED);
	}

	/**
	 * This routine is used to copy standard header and library files into the
	 * generated code.
	 */
	private void  cg_sys( String filename){		 /* File name */
		File libfile = new File(filename);
		
		if(!libfile.exists()){
			System.err.println( "cg_sys: cannot open system file" );
	      	System.exit(1);
		}
		try{
			BufferedReader in = new BufferedReader(new FileReader(libfile));
			String line;
			while((line = in.readLine()) != null){
				out.println(line);
			}
		} catch (IOException e) {
			System.err.println("cg_sys: problem with library file");
			System.exit(1);
		}
	  
	}
	
	/**
	 * This routine is used to create the constant area in memory
	 */
    private void cg_const(Vector<Data3a> suite){
	Data3a courant;
	String t; int i; 
	
	for(int ic = 0;ic<suite.size();ic++) {
	    courant = suite.elementAt(ic);
	    out.println(courant.getLabel().getName3a()+":") ;
	    t = courant.getString(); /* the text */
	    for( i = 0 ;  i<t.length(); i++ )
		if( t.charAt(i) == '\\' )
		    switch( t.charAt(++i) )
			{
			case 'n':      
			    out.println(R_INDENT + "DB   "+(int)'\n' ) ;
			    break ;
			    
			case '\"':
			    out.println(R_INDENT + "DB   "+(int)'\\') ;
			    break ;
			}
		else{
		    int chval =  (int)(t.charAt(i));
		    out.println( R_INDENT +"DB   "+ chval+"   \\ "+t.charAt(i) ) ;
		    
		}
	    out.println( R_INDENT +"DB   0") ;	 /* End of string */
	}
	out.println( "L0:" ) ; /* Begin of stack */
    }
}
